/**
 * 
 */
package jvs.vfs;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.net.URI;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import x.java.io.File;
import x.java.io.FileOutputStream;

/**
 * @author qiangli
 * 
 */
public class FileMan {
	public static class Status {
		public boolean aborted;
		public String message;
	}
	
	private static FileMan man = new FileMan();

	public static FileMan getInstance() {
		return man;
	}

	protected FileMan() {
	}

	protected File createFile(URI uri) throws IOException {
		return new x.java.io.File(uri);
	}

	protected File createFile(File parent, String name) throws IOException {
		return new x.java.io.File(parent, name);
	}

	protected InputStream createInputStream(File file) throws IOException {
		return new x.java.io.FileInputStream(file);
	}

	protected OutputStream createOutputStream(File file) throws IOException {
		return new x.java.io.FileOutputStream(file);
	}

	protected Reader createReader(File file) throws IOException {
		return new x.java.io.FileReader(file);
	}

	protected Writer createWriter(File file) throws IOException {
		return new x.java.io.FileWriter(file);
	}

	public File createTempFile(String prefix, String suffix) throws IOException {
		return (File) File.createTempFile(prefix, suffix);
	}

	public synchronized File createTempDirectory(String prefix, String suffix)
			throws IOException {
		long cnt = System.currentTimeMillis();
		while (true) {
			File f = (File) File.createTempFile(prefix + (cnt++), suffix);
			if (f.delete() && f.mkdirs()) {
				return f;
			}
		}
	}

	public void copy(File from, File to) throws IOException {
		BufferedInputStream is = createBufferedInputStream(from);
		BufferedOutputStream os = createBufferedOutputStream(to);

		copy(is, os, true);
	}

	public void copy(InputStream in, OutputStream out, boolean close)
			throws IOException {
		int read = -1;
		byte[] b = new byte[File.BUFSIZE];
		while ((read = in.read(b)) != -1) {
			out.write(b, 0, read);
		}
		if (close) {
			in.close();
			out.close();
		}
	}

	public int count(InputStream in) throws IOException {
		int read = -1;
		byte[] b = new byte[File.BUFSIZE];
		int cnt = 0;
		while ((read = in.read(b)) != -1) {
			cnt += read;
		}
		return cnt;
	}

	public void touch(File file, long now, Status status) {
		if (status != null && status.aborted) {
			throw new RuntimeException("Operation aborted.");
		}
		file.setLastModified(now);
		if (file.isDirectory()) {
			File[] files = file.listFiles();
			for (int i = 0; i < files.length; i++) {
				touch(files[i], now, status);
			}
		}
	}

	public void zip(File file, File zip) throws IOException {
		ZipOutputStream out = new ZipOutputStream(
				createBufferedOutputStream(zip));
		if (file.isDirectory()) {
			File[] files = file.listFiles();
			for (int i = 0; i < files.length; i++) {
				if (files[i].isDirectory()) {
					continue;// Ignore
				}
				InputStream in = createBufferedInputStream(files[i]);
				ZipEntry ze = new ZipEntry(files[i].getPath());
				out.putNextEntry(ze);
				copy(in, out, false);
				in.close();
			}
		} else {
			InputStream in = createBufferedInputStream(file);
			copy(in, out, false);
			in.close();
		}
		out.close();
	}

	public void df(StringBuffer out) {
		// TODO
	}

//	public long du(File file, Collector<File> trail, Status status) {
//		long[] t = new long[1];
//		du(file, t, trail, status);
//		return t[0];
//	}
//
//	private void du(File file, long[] total, Collector<File> trail, Status status) {
//		if (status != null && status.aborted) {
//			throw new RuntimeException("DU operation aborted.");
//		}
//		if (file.isFile()) {
//			total[0] += file.length();
//			return;
//		}
//		if (trail != null) {
//			trail.add(file);
//		}
//		File[] d = file.listFiles();
//		for (int i = 0; i < d.length; i++) {
//			du(d[i], total, trail, status);
//		}
//	}
//
//	public void grep(File file, String regex, Collector<String> out)
//			throws IOException {
//		InputStream in = createBufferedInputStream(file);
//		Pattern p = Pattern.compile(regex);
//		BufferedReader r = new BufferedReader(new InputStreamReader(in),
//				File.BUFSIZE);
//		String l;
//		for (l = r.readLine(); l != null; l = r.readLine()) {
//			Matcher m = p.matcher(l);
//			if (m.find()) {
//				out.add(l);
//			}
//		}
//	}

	public void splice(File[] files, File out) throws IOException {
		OutputStream os = createBufferedOutputStream(out);
		for (File f : files) {
			InputStream is = createBufferedInputStream(f);
			copy(is, os, true);
		}
		os.close();
	}

	public File[] split(File file, long size, File out) throws IOException {
		long len = file.length();
		int cnt = (int) (len / size) + 1;
		if (cnt == 1) {
			return new File[] { file };
		}
		if (!out.mkdirs()) {
			throw new IOException("Create: " + out);
		}
		File[] files = new File[cnt];
		String name = file.getName();
		InputStream is = createBufferedInputStream(file);
		for (int i = 0; i < cnt; i++) {
			files[i] = createFile(out, name + "." + i);
			OutputStream os = createBufferedOutputStream(files[i]);
			for (int j = 0; j < size; j++) {
				int b = is.read();
				os.write((byte) b);
			}
			os.close();
		}
		is.close();
		return files;
	}

//	public void find(File dir, String pattern, Collector<File> out, Collector<File> trail, Status status) {
//		Pattern p = Pattern.compile(pattern);
//		find(dir, p, out, trail, status);
//	}

//	private void find(File dir, Pattern pattern, Collector<File> out,
//			Collector<File> trail, Status status) {
//		if (status != null && status.aborted) {
//			throw new RuntimeException("FIND operation aborted.");
//		}
//		if (dir.isFile()) {
//			if (pattern.matcher(dir.getName()).matches()) {
//				out.add(dir);
//			}
//			return;
//		}
//		if (trail != null) {
//			trail.add(dir);
//		}
//		File[] files = dir.listFiles();
//		for (File f : files) {
//			find(f, pattern, out, trail, status);
//		}
//	}
//
//	public void walk(File dir, FileFilter filter, Collector<File> out,
//			Collector<File> trail, Status status) {
//		if (status != null && status.aborted) {
//			throw new RuntimeException("Operation aborted.");
//		}
//		if (dir.isFile()) {
//			if (filter.accept(dir) && out != null) {
//				out.add(dir);
//			}
//			return;
//		}
//		if (trail != null) {
//			trail.add(dir);
//		}
//		File[] files = dir.listFiles();
//		for (File f : files) {
//			walk(f, filter, out, trail, status);
//		}
//	}

	public String read(File f) throws IOException {
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		InputStream in = createInputStream(f);
		copy(in, out, true);
		return out.toString();
	}

	public String readlines(File file, int max) throws IOException {
		StringBuffer sb = new StringBuffer();
		BufferedReader in = createBufferedReader(file);
		String s = null;
		int cnt = 0;
		while ((s = in.readLine()) != null && cnt < max) {
			sb.append(s);
			sb.append("\r\n");
			cnt++;
		}
		in.close();
		return (sb.toString());
	}

	public void saveas(String s, File file) throws IOException {
		ByteArrayInputStream is = new ByteArrayInputStream(s.getBytes());
		saveas(is, file);
	}

	public void saveas(InputStream is, File file) throws IOException {
		boolean exist = file.exists();
		if (!exist) {
			OutputStream os = new FileOutputStream(file);
			copy(is, os, false);
			os.close();
			return;
		}
		//exists
		
		String p = file.getPath();
		File old = new File(p + "_old");
		if (old.exists() && !old.renameTo(new File(p + "_old_" + System.currentTimeMillis()))) {
			throw new IOException("Failed to backup _old file. "+old.getPath());
		}
		if (file.renameTo(old)) {
			OutputStream os = new FileOutputStream(file);
			copy(is, os, false);
			os.close();
			return;
		}
		throw new IOException("Failed to rename existing file. "+file.getPath());
	}

	private BufferedInputStream createBufferedInputStream(File file)
			throws IOException {
		return new BufferedInputStream(createInputStream(file), File.BUFSIZE);
	}

	private BufferedOutputStream createBufferedOutputStream(File file)
			throws IOException {
		return new BufferedOutputStream(createOutputStream(file), File.BUFSIZE);
	}

	private BufferedReader createBufferedReader(File file) throws IOException {
		return new BufferedReader(createReader(file), File.BUFSIZE);
	}

}
